package com.xdb.sqlparser.builder.impl;

import java.util.List;

import com.xdb.sqlparser.ast.SQLExpr;
import com.xdb.sqlparser.ast.SQLOrderBy;
import com.xdb.sqlparser.ast.SQLStatement;
import com.xdb.sqlparser.ast.expr.SQLBinaryOperator;
import com.xdb.sqlparser.ast.expr.SQLIdentifierExpr;
import com.xdb.sqlparser.ast.expr.SQLIntegerExpr;
import com.xdb.sqlparser.ast.statement.SQLExprTableSource;
import com.xdb.sqlparser.ast.statement.SQLSelect;
import com.xdb.sqlparser.ast.statement.SQLSelectGroupByClause;
import com.xdb.sqlparser.ast.statement.SQLSelectItem;
import com.xdb.sqlparser.ast.statement.SQLSelectOrderByItem;
import com.xdb.sqlparser.ast.statement.SQLSelectQuery;
import com.xdb.sqlparser.ast.statement.SQLSelectQueryBlock;
import com.xdb.sqlparser.ast.statement.SQLSelectStatement;
import com.xdb.sqlparser.builder.SQLSelectBuilder;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlSelectGroupBy;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit;
import com.xdb.sqlparser.dialect.oracle.ast.OracleOrderBy;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelect;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectQueryBlock;
import com.xdb.sqlparser.util.SQLUtils;
import com.xdb.util.JDBCConstants;

public class SQLSelectBuilderImpl implements SQLSelectBuilder {

	private SQLSelectStatement stmt;
	private String dbType;

	public SQLSelectBuilderImpl(String dbType) {
		this(new SQLSelectStatement(), dbType);
	}

	public SQLSelectBuilderImpl(String sql, String dbType) {
		List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);

		if (stmtList.size() == 0) {
			throw new IllegalArgumentException("not support empty-statement :" + sql);
		}

		if (stmtList.size() > 1) {
			throw new IllegalArgumentException("not support multi-statement :" + sql);
		}

		SQLSelectStatement stmt = (SQLSelectStatement) stmtList.get(0);
		this.stmt = stmt;
		this.dbType = dbType;
	}

	public SQLSelectBuilderImpl(SQLSelectStatement stmt, String dbType) {
		this.stmt = stmt;
		this.dbType = dbType;
	}

	public SQLSelect getSQLSelect() {
		if (stmt.getSelect() == null) {
			stmt.setSelect(createSelect());
		}
		return stmt.getSelect();
	}

	@Override
	public SQLSelectStatement getSQLSelectStatement() {
		return stmt;
	}

	public SQLSelectBuilderImpl select(String... columns) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		for (String column : columns) {
			SQLSelectItem selectItem = SQLUtils.toSelectItem(column, dbType);
			queryBlock.addSelectItem(selectItem);
		}

		return this;
	}

	@Override
	public SQLSelectBuilderImpl selectWithAlias(String column, String alias) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLExpr columnExpr = SQLUtils.toSQLExpr(column, dbType);
		SQLSelectItem selectItem = new SQLSelectItem(columnExpr, alias);
		queryBlock.addSelectItem(selectItem);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl from(String table) {
		return from(table, null);
	}

	@Override
	public SQLSelectBuilderImpl from(String table, String alias) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();
		SQLExprTableSource from = new SQLExprTableSource(new SQLIdentifierExpr(table), alias);
		queryBlock.setFrom(from);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl orderBy(String... columns) {
		SQLSelect select = this.getSQLSelect();

		SQLOrderBy orderBy = select.getOrderBy();
		if (orderBy == null) {
			orderBy = createOrderBy();
			select.setOrderBy(orderBy);
		}

		for (String column : columns) {
			SQLSelectOrderByItem orderByItem = SQLUtils.toOrderByItem(column, dbType);
			orderBy.addItem(orderByItem);
		}

		return this;
	}

	@Override
	public SQLSelectBuilderImpl groupBy(String expr) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLSelectGroupByClause groupBy = queryBlock.getGroupBy();
		if (groupBy == null) {
			groupBy = createGroupBy();
			queryBlock.setGroupBy(groupBy);
		}

		SQLExpr exprObj = SQLUtils.toSQLExpr(expr, dbType);
		groupBy.addItem(exprObj);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl having(String expr) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLSelectGroupByClause groupBy = queryBlock.getGroupBy();
		if (groupBy == null) {
			groupBy = createGroupBy();
			queryBlock.setGroupBy(groupBy);
		}

		SQLExpr exprObj = SQLUtils.toSQLExpr(expr, dbType);
		groupBy.setHaving(exprObj);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl into(String expr) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLExpr exprObj = SQLUtils.toSQLExpr(expr, dbType);
		queryBlock.setInto(exprObj);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl where(String expr) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLExpr exprObj = SQLUtils.toSQLExpr(expr, dbType);
		queryBlock.setWhere(exprObj);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl whereAnd(String expr) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLExpr exprObj = SQLUtils.toSQLExpr(expr, dbType);
		SQLExpr newCondition = SQLUtils.buildCondition(SQLBinaryOperator.BooleanAnd, exprObj, false, queryBlock.getWhere());
		queryBlock.setWhere(newCondition);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl whereOr(String expr) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		SQLExpr exprObj = SQLUtils.toSQLExpr(expr, dbType);
		SQLExpr newCondition = SQLUtils.buildCondition(SQLBinaryOperator.BooleanOr, exprObj, false, queryBlock.getWhere());
		queryBlock.setWhere(newCondition);

		return this;
	}

	@Override
	public SQLSelectBuilderImpl limit(int rowCount) {
		return limit(rowCount, 0);
	}

	@Override
	public SQLSelectBuilderImpl limit(int rowCount, int offset) {
		SQLSelectQueryBlock queryBlock = getQueryBlock();

		if (queryBlock instanceof MySqlSelectQueryBlock) {
			MySqlSelectQueryBlock mySqlQueryBlock = (MySqlSelectQueryBlock) queryBlock;

			Limit limit = new Limit();
			limit.setRowCount(new SQLIntegerExpr(rowCount));
			if (offset > 0) {
				limit.setOffset(new SQLIntegerExpr(offset));
			}

			mySqlQueryBlock.setLimit(limit);

			return this;
		}

		if (queryBlock instanceof OracleSelectQueryBlock) {
			OracleSelectQueryBlock oracleQueryBlock = (OracleSelectQueryBlock) queryBlock;
			if (offset <= 0) {
				SQLExpr rowCountExpr = new SQLIntegerExpr(rowCount);
				SQLExpr newCondition = SQLUtils.buildCondition(SQLBinaryOperator.BooleanAnd, rowCountExpr, false, oracleQueryBlock.getWhere());
				queryBlock.setWhere(newCondition);
			} else {
				throw new UnsupportedOperationException("not support offset");
			}

			return this;
		}

		throw new UnsupportedOperationException();
	}

	protected SQLSelectQueryBlock getQueryBlock() {
		SQLSelect select = getSQLSelect();
		SQLSelectQuery query = select.getQuery();
		if (query == null) {
			query = createSelectQueryBlock();
			select.setQuery(query);
		}

		if (!(query instanceof SQLSelectQueryBlock)) {
			throw new IllegalStateException("not support from, class : " + query.getClass().getName());
		}

		SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) query;
		return queryBlock;
	}

	protected SQLSelect createSelect() {
		if (JDBCConstants.ORACLE.equals(dbType)) {
			return new OracleSelect();
		}

		return new SQLSelect();
	}

	protected SQLSelectQuery createSelectQueryBlock() {
		if (JDBCConstants.MYSQL.equals(dbType)) {
			return new MySqlSelectQueryBlock();
		}

		if (JDBCConstants.ORACLE.equals(dbType)) {
			return new OracleSelectQueryBlock();
		}

		return new SQLSelectQueryBlock();
	}

	protected SQLOrderBy createOrderBy() {

		if (JDBCConstants.ORACLE.equals(dbType)) {
			return new OracleOrderBy();
		}

		return new SQLOrderBy();
	}

	protected SQLSelectGroupByClause createGroupBy() {
		if (JDBCConstants.MYSQL.equals(dbType)) {
			return new MySqlSelectGroupBy();
		}

		return new SQLSelectGroupByClause();
	}

	public String toString() {
		return SQLUtils.toSQLString(stmt, dbType);
	}
}
